<?php

namespace app\core\services;

use DomainException;
use Exception;
use ReflectionException;
use Yii;
use yii\helpers\ArrayHelper;
use yii\rbac\ManagerInterface;
use yii\rbac\Role;
use yii\helpers\Json;

class RoleManager
{
    private ManagerInterface $manager;

    public function __construct(ManagerInterface $manager)
    {
        $this->manager = $manager;
    }

    /**
     * @throws Exception
     */
    public function assign($userId, $name): void
    {
        $am = $this->manager;
        $am->revokeAll($userId);
        if (!$role = $am->getRole($name)) {
            throw new DomainException('Role "' . $name . '" does not exist.');
        }
        $am->revokeAll($userId);
        $am->assign($role, $userId);
    }

    public function create($name, $description = '', $ruleName = null, $data = null): void
    {
        $am = $this->manager;
        if ($role = $am->getRole($name)) {
            throw new DomainException('Role "' . $name . '" is already exist.');
        }
        $newRole = $am->createRole($name);
        $newRole->description = $description;
        $newRole->data = $data == null ? null : Json::decode($data);
        $newRole->ruleName = empty($ruleName) ? null : $ruleName;
        try {
            $am->add($newRole);
        }
        catch (ReflectionException|Exception $e) {
            throw new DomainException($e->getMessage());
        }
    }

    public function update($name, $newName, $description = '', $ruleName = null, $data = null): void
    {
        $am = $this->manager;
        if (!$role = $am->getRole($name)) {
            throw new DomainException('Role "' . $name . '" does not exist.');
        }
        if (($name == 'admin' || $name == 'user') && $name != $newName)
        {
            throw new DomainException('Role "' . $name . '" can not be renamed.');
        }
        $role->name = $newName;
        $role->description = $description;
        $role->ruleName = empty($ruleName) ? null : $ruleName;
        $role->data = $data == null ? null : Json::decode($data);
        try {
            $am->update($name, $role);
        }
        catch (ReflectionException|Exception $e) {
            throw new DomainException($e->getMessage());
        }
    }

    public function delete($name): void
    {
        $am = $this->manager;
        if (!$role = $am->getRole($name)) {
            throw new DomainException('Role "' . $name . '" does not exist.');
        }
        if ($role->name == 'admin' || $role->name == 'user') {
            throw new DomainException('Can not delete role "' . $name . '"');
        }
        $am->remove($role);
    }

    /**
     * @throws \yii\base\Exception
     */
    public function child($parentRoleName, $childRoleName): void
    {
        $am = $this->manager;
        if (!$parentRole = $am->getRole($parentRoleName)) {
            throw new DomainException('Parent role "' . $parentRoleName . '" does not exist.');
        }
        if (!$childRole = $am->getRole($childRoleName)) {
            throw new DomainException('Child role "' . $childRoleName . '" does not exist.');
        }
        if ($parentRoleName == $childRoleName) {
            throw new DomainException('Can not add a role to yourself.');
        }
        $am->addChild($parentRole, $childRole);
    }

    public function killchild($parentRoleName, $childRoleName): void
    {
        $am = $this->manager;
        if (!$parentRole = $am->getRole($parentRoleName)) {
            throw new DomainException('Parent role "' . $parentRoleName . '" does not exist.');
        }
        if (!$childRole = $am->getRole($childRoleName)) {
            throw new DomainException('Child role "' . $childRoleName . '" does not exist.');
        }
        if ($parentRoleName == $childRoleName) {
            throw new DomainException('Can not kill yourself as child.');
        }
        if (!$am->hasChild($parentRole, $childRole)) {
            throw new DomainException('Role "' . $childRoleName . '" does not assigned to "' . $parentRoleName . '".');
        }
        $am->removeChild($parentRole, $childRole);
    }

    public function getRoles(): array
    {
        $am = $this->manager;
        return $am->getRoles();
    }

    public function getRole($name): Role
    {
        $am = $this->manager;
        if (!$role = $am->getRole($name)) {
            throw new DomainException('Role "' . $name . '" does not exist.');
        }
        return $role;
    }

    public function getChildRoles($name): array
    {
        $am = $this->manager;
        return $am->getChildRoles($name);
    }

    public function getRolesNamesByUser($id): array
    {
        $am = $this->manager;
        $roles = $am->getRolesByUser($id);
        return array_map(function(Role $role){
            return $role->name;
        }, $roles);
    }

    public function getRolesListArray(): array
    {
        return array_map(function (Role $role){
            return [
                'name' => $role->name,
                'description' => $role->description,
            ];
        }, $this->getRoles());
    }

    public function getRolesSelectArray(): array
    {
        return ArrayHelper::map($this->getRoles(), 'name', 'description');
    }

    public function getRolesSelectArrayByRole($id): array
    {
        $am = $this->manager;
        return ArrayHelper::getColumn($am->getChildRoles($id), 'name');
    }

    /**
     * @throws \yii\base\Exception
     */
    public function saveChildren($roleName, $rolesNames, $permissionNames): void
    {
        $am = $this->manager;
        $role = $am->getRole($roleName);
        $am->removeChildren($role);

        if (is_array($rolesNames) && !empty($rolesNames)) {
            foreach ($rolesNames as $name) {
                $childRole = $am->getRole($name);
                $am->addChild($role, $childRole);
            }
        }

        if (is_array($permissionNames) && !empty($permissionNames)) {
            foreach ($permissionNames as $name) {
                $childPermission = $am->getPermission($name);
                $am->addChild($role, $childPermission);
            }
        }
    }

    public static function getCurrentRoleName(): ?string
    {
        $roles = Yii::$app->authManager->getRolesByUser(Yii::$app->user->id);
        if (!$roles) {
            return null;
        }
        reset($roles);
        /* @var $role Role */
        $role = current($roles);

        return $role->name;
    }
}
